import random
import time

from base.meta import SpriteMeta
from base import common
from manager import sprite_mgr
from settings import *
from timer import Timer


class BaseSprite(PG.sprite.Sprite, metaclass=SpriteMeta):

    def __init__(self, pos, *group, index=0, blood=False):
        super().__init__(*group)
        self.action = getattr(self, "default_active")
        self.index = index
        self.area = None
        self.image = self.action.images[self.index]
        self.rect = self.image.get_rect(center=pos)

        self.direction = PG.math.Vector2()
        self.pos = PG.math.Vector2(self.rect.center)
        self.destruct = False
        self.damage = None
        self.can_damaged = True
        if blood:
            self.health = 1
            self.blood_di = sprite_mgr.sprites_cls["PlayerDiBlood"](self.rect,
                                                                    sprite_mgr.get_group(PGGroup.update.value))
            self.blood = sprite_mgr.sprites_cls["PlayerXueBlood"](self.rect, sprite_mgr.get_group(PGGroup.update.value))
        else:
            self.blood = None

    def __str__(self):
        return self.oid

    def set_act(self, act):
        self.action = act

    def animate(self, dt):
        if self.action.active:
            self.index += 4 * dt
            if self.index >= self.action.images_length:
                self.index = 0
            self.image = self.action.images[int(self.index)]

    def move(self, dt):
        # normalizing a vector
        if self.direction.x == self.direction.y == 0:
            return
        if self.direction.magnitude() > 0:
            self.direction = self.direction.normalize()
        # horizontal movement
        self.pos.x += self.direction.x * self.speed * dt
        self.hit_box.centerx = round(self.pos.x)
        self.rect.centerx = self.hit_box.centerx
        self.collision("horizontal")

        # vertical movement
        self.pos.y += self.direction.y * self.speed * dt
        self.hit_box.centery = round(self.pos.y)
        self.rect.centery = self.hit_box.centery
        self.collision("vertical")
        if self.blood:
            self.blood.move(self.rect.copy(), self.health)
            self.blood_di.move(self.rect.copy())


class Player(BaseSprite):
    def __init__(self, name, pos, *group, index=0, blood=True):
        super().__init__(pos, *group, index=index, blood=blood)
        self.name = name
        self.z = PGLayers.main.value
        self.defense = 20
        self.damage = 20
        self.speed = 200
        self.life = True
        self.hit_box = self.rect.copy().inflate(-self.rect.width * 0.30, -self.rect.height * 0.30)

        # arms
        self.arms = ['Bullet1', 'Bullet2', 'Bullet3']
        self.skills = ['skill-1', 'skill-2', 'skill-3']
        self.arm_index = 0
        self.skill_index = 0
        self.collision_group = sprite_mgr.get_group(PGGroup.collision.value)

        self.timers = {
            'arm use': Timer(1, self.use_arm),
            'arm switch': Timer(100, self.switch_arm),
            'skill use': Timer(10, self.use_skill),
            'skill switch': Timer(100, self.switch_skill)
        }

    def update_timers(self):
        for timer in self.timers.values():
            timer.update()

    def use_arm(self, name):
        sprite_mgr.sprites_cls[name]((self.rect.centerx, self.rect.centery),
                                     sprite_mgr.get_group(PGGroup.update.value),
                                     sprite_mgr.get_group(PGGroup.collision.value),
                                     direction=self.action.direction)

    def use_skill(self):
        print(f"use skill {self.skill_index}")

    def switch_arm(self):
        if self.arm_index < len(self.arms) - 1:
            self.arm_index += 1
        else:
            self.arm_index = 0

    def switch_skill(self):
        print(f"switch skill {self.skill_index}")
        if self.skill_index < len(self.skills) - 1:
            self.skill_index += 1
        else:
            self.skill_index = 0

    @staticmethod
    def handle_bullet(self, bullet_sprite):
        pass

    def collision(self, direction):
        for sprite in self.collision_group.sprites():
            if sprite.hit_box.colliderect(self.hit_box):
                if isinstance(sprite, sprite_mgr.sprites_cls["GenericStatic"]):
                    common.waylay(self, sprite, direction)
                elif isinstance(sprite, sprite_mgr.base_sprites_cls["Enemy"]):
                    common.eject_self(self, sprite, direction)
                    common.damage(self, sprite)

    def input(self):
        keys = PG.key.get_pressed()
        if self.life and keys:
            if keys[PG.K_UP]:
                self.set_act(self.to_up)
                self.direction.y = -1
            elif keys[PG.K_DOWN]:
                self.set_act(self.to_down)
                self.direction.y = 1
            else:
                self.direction.y = 0
            if keys[PG.K_LEFT]:
                self.set_act(self.to_left)
                self.direction.x = -1
            elif keys[PG.K_RIGHT]:
                self.set_act(self.to_right)
                self.direction.x = 1
            else:
                self.direction.x = 0
            # tool use
            if keys[PG.K_SPACE]:
                self.timers['arm use'].activate(self.arms[self.arm_index])
            if keys[PG.K_q]:
                self.timers['arm switch'].activate()
            if keys[PG.K_e]:
                self.timers['skill switch'].activate()
            if keys[PG.K_RCTRL]:
                self.timers['skill use'].activate()

    def update(self, dt) -> None:
        self.input()
        self.update_timers()
        if not self.life:
            return
        if self.health <= 0:
            self.life = False
            self.image = self.to_die.images[0]
            return
        self.move(dt)
        self.animate(dt)


class Enemy(BaseSprite):
    def __init__(self, pos, *group, index=0):
        super().__init__(pos, *group, index=index)
        self.z = PGLayers.main.value
        self.health = 1
        self.defense = 10
        self.damage = 30
        self.speed = 100
        self.life = True
        self.hit_box = self.rect.copy().inflate(-self.rect.width * 0.30, -self.rect.height * 0.30)
        self.damage_target = random.choice(sprite_mgr.get_group(PGGroup.player.value).sprites())
        self.collision_group = sprite_mgr.get_group(PGGroup.collision.value)
        self.enemy_group = sprite_mgr.get_group(PGGroup.enemy.value)
        self.player_group = sprite_mgr.get_group(PGGroup.player.value)

    def update(self, dt) -> None:
        if not self.life:
            return
        if self.health <= 0:
            self.life = False
            self.image = self.to_die.images[0]
            return
        self.check_collision()
        self.move(dt)
        self.animate(dt)

    def collision(self, direction):
        pass

    @staticmethod
    def _collided(a, b):
        if not (a is b):
            return a.hit_box.colliderect(b.hit_box)

    def check_collision(self):
        result = PG.sprite.spritecollide(self, self.collision_group, False, self._collided)


class Bullet(BaseSprite):
    def __init__(self, pos, *group, index=0, direction=None):
        super().__init__(pos, *group, index=index)
        self.can_damaged = False
        self.z = PGLayers.main.value
        self.direction = direction
        self.damage = self.__class__.damage
        self.speed = 500
        self.hit_box = self.rect
        self.player_group = sprite_mgr.get_group(PGGroup.player.value)
        self.enemy_group = sprite_mgr.get_group(PGGroup.enemy.value)

    def move(self, dt):
        if self.direction.magnitude() > 0:
            self.direction = self.direction.normalize()

        # horizontal movement
        self.pos.x += self.direction.x * self.speed * dt
        self.rect.centerx = self.pos.x

        # vertical movement
        self.pos.y += self.direction.y * self.speed * dt
        self.rect.centery = self.pos.y
        if any(
                [self.rect.left < 0,
                 self.rect.right > SCREEN_WIDTH,
                 self.rect.top < 0,
                 self.rect.bottom > SCREEN_HEIGHT]
        ):
            self.kill()

    def update(self, dt) -> None:
        self.move(dt)
        self.check_collision()
        self.animate(dt)

    @staticmethod
    def _collided(a, b):
        if a.hit_box.colliderect(b.hit_box):
            vec = "vertical" if a.direction.y else "horizontal"
            common.eject_other(a, b, vec)
            common.damage(b, a)
            a.kill()
            return True
        return False

    def check_collision(self):
        PG.sprite.spritecollide(self, self.enemy_group, False, self._collided)


class Blood(BaseSprite):
    def __init__(self, rect, *group, index=0):
        x = rect.centerx
        y = rect.top - 5
        super().__init__((x, y), *group, index=index)
        self.can_damaged = False
        self.z = PGLayers.main.value
        self.area = [0, 0, self.rect.width, self.rect.height]
        self.blood_length = self.area[2]
